// This script is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
// https://creativecommons.org/licenses/by-nc-sa/4.0/
// © QuantAlgo

//@version=6
indicator("CUSUM Drift Trend [QuantAlgo]", overlay = true)

//              ╔════════════════════════════════╗              //
//              ║      USER-DEFINED SETTINGS     ║              //
//              ╚════════════════════════════════╝              //

var string cusum_settings  = "════════ CUSUM Settings ════════"
var string visual_settings = "════════ Visualization Settings ════════"

tooltip_preset       = "Select a predefined configuration for different trading styles and timeframes. Fast Response and Smooth Trend set the Volatility Length, Drift Allowance, and Decision Threshold, overriding those inputs below. Source and all visualization inputs keep their manual values."
tooltip_preset_det   = "Default: Uses the manual settings below, with the Volatility Length, Drift Allowance, and Decision Threshold adjustable directly. Suited to swing trading on 1H to daily charts.\n\nFast Response (Length 14, Allowance 0.15, Threshold 2.5): Shortens the volatility window and lowers the allowance and threshold, so trend changes confirm after less accumulated evidence and the trend reacts sooner. Suited to intraday trading on 5min to 1H charts.\n\nSmooth Trend (Length 40, Allowance 0.3, Threshold 5.0): Lengthens the volatility window and raises the allowance and threshold, so only larger and more persistent drifts confirm a trend change and trends hold through deeper pullbacks. Suited to position trading on daily and weekly charts."
tooltip_volatility   = "The span of the exponentially weighted volatility estimate that standardizes every bar's price change, so evidence is measured in volatility units on any market. Shorter spans adjust the scale quickly when volatility shifts. Longer spans hold a steadier scale, so a sudden expansion in volatility registers as larger standardized moves until the estimate adapts."
tooltip_allowance    = "The reference drift, in volatility units, subtracted from every standardized move before it is accumulated. Moves smaller than the allowance drain a cumulative sum rather than build it, so the allowance sets the smallest persistent drift the engine treats as directional. Higher values filter out slow drifts and respond to stronger moves. Lower values let gradual drifts build evidence over time."
tooltip_threshold    = "The decision interval, in volatility units, that the opposing cumulative sum must exceed on a closed bar to confirm a trend change. Higher values require more accumulated evidence and produce fewer, later trend changes. Lower values confirm sooner and produce more trend changes."
tooltip_source       = "The price series the engine evaluates. Each bar's change in this series is scaled by recent volatility before it enters the cumulative sums. Close measures settled bar values, while averaged sources such as HL2 or HLC3 soften the effect of individual closing prints."
tooltip_color_preset = "Pre-configured color schemes for different chart themes and visual preferences. Classic uses traditional green and red. Aqua provides ocean-inspired blue and orange. Cosmic offers futuristic cyan and purple. Cyber features cool cyan and warm orange contrast. Neon delivers high-contrast yellow and magenta for maximum visibility. Custom uses the Bullish and Bearish colors set below."
tooltip_bullish      = "Color applied to the boundary line, boundary glow, evidence zone, trend change points, price bars, and background while the trend is bullish, which is confirmed on the closed bar where the upside cumulative sum exceeds the Decision Threshold. Use a vibrant color to highlight uptrend phases and potential long opportunities. Only used when Color Preset is set to Custom."
tooltip_bearish      = "Color applied to the boundary line, boundary glow, evidence zone, trend change points, price bars, and background while the trend is bearish, which is confirmed on the closed bar where the downside cumulative sum exceeds the Decision Threshold. Use a warning color to highlight downtrend phases and potential short opportunities or exits. Only used when Color Preset is set to Custom."
tooltip_glow         = "Enable/disable the soft glow around the boundary line, which helps the active boundary stand out against price. Disable for a thinner line that keeps the focus on price action."
tooltip_zone         = "Enable/disable the shading between price and the CUSUM boundary. When enabled, the zone is filled with the current trend color and deepens as evidence for a reversal accumulates, growing stronger while the opposing cumulative sum climbs toward the Decision Threshold and fading as the prevailing trend resumes. The zone narrows as that evidence builds, so the room left before a trend change is visible at a glance."
tooltip_points       = "Enable/disable the glowing point that opens each new boundary line. Each point sits on the bar where a bullish or bearish trend is confirmed, at the level where the new boundary begins, so every boundary line starts from a visible anchor. A trend that reverses on the very next bar keeps its point but draws no line, since its boundary lasts only one bar. Disable for an uncluttered chart where the boundary line color, bar coloring, or alerts track the trend instead."
tooltip_barcolor     = "Enable/disable bar coloring based on the current trend. When enabled, price bars are tinted with the bullish or bearish color, providing instant visual confirmation of the trend without reading the boundary line directly. Disable if you prefer default bar colors or use another bar coloring indicator."
tooltip_bartrans     = "Transparency of the bar coloring overlay. Lower values (0-30) produce vivid, fully saturated bars that make the trend immediately obvious. Higher values (70-90) apply a subtle tint that shows the trend without overwhelming price action. Only applies when Enable Bar Coloring is turned on."
tooltip_bgcolor      = "Enable/disable background coloring based on the current trend. When enabled, the chart background is shaded with the bullish or bearish color, highlighting each trend across the full height of the chart. Disable if you prefer a clean chart background or use another background coloring indicator."
tooltip_bgtrans      = "Transparency of the background coloring. Lower values (50-70) produce strong shading that makes each trend stand out immediately. Higher values (85-95) apply a subtle tint that keeps price action and other chart elements easy to read. Only applies when Enable Background Coloring is turned on."

presetMode  = input.string("Default", "Preset Configuration", group = cusum_settings, options = ["Default", "Fast Response", "Smooth Trend"], tooltip = tooltip_preset + "\n\n" + tooltip_preset_det)
volLenIn    = input.int(20, "Volatility Length", group = cusum_settings, minval = 2, tooltip = tooltip_volatility)
allowanceIn = input.float(0.2, "Drift Allowance", group = cusum_settings, minval = 0.0, step = 0.05, tooltip = tooltip_allowance)
thresholdIn = input.float(3.5, "Decision Threshold", group = cusum_settings, minval = 0.5, step = 0.25, tooltip = tooltip_threshold)
src         = input.source(close, "Source", group = cusum_settings, tooltip = tooltip_source)

colorPreset = input.string("Custom", "Color Preset", group = visual_settings, options = ["Custom", "Classic", "Aqua", "Cosmic", "Cyber", "Neon"], tooltip = tooltip_color_preset)
bullCustom  = input.color(#00ffaa, "Bullish Color", group = visual_settings, tooltip = tooltip_bullish)
bearCustom  = input.color(#ff0000, "Bearish Color", group = visual_settings, tooltip = tooltip_bearish)
showGlow    = input.bool(true, "Show Boundary Glow", group = visual_settings, tooltip = tooltip_glow)
showZone    = input.bool(true, "Show Evidence Zone", group = visual_settings, tooltip = tooltip_zone)
showPoints  = input.bool(true, "Show Trend Change Points", group = visual_settings, tooltip = tooltip_points)
colorBars   = input.bool(false, "Enable Bar Coloring", group = visual_settings, tooltip = tooltip_barcolor)
barTrans    = input.int(0, "Bar Color Transparency", group = visual_settings, minval = 0, maxval = 100, tooltip = tooltip_bartrans)
colorBg     = input.bool(false, "Enable Background Coloring", group = visual_settings, tooltip = tooltip_bgcolor)
bgTrans     = input.int(90, "Background Color Transparency", group = visual_settings, minval = 0, maxval = 100, tooltip = tooltip_bgtrans)

//              ╔════════════════════════════════╗              //
//              ║      PRESET CONFIGURATION      ║              //
//              ╚════════════════════════════════╝              //

int volLen = switch presetMode
    "Fast Response" => 14
    "Smooth Trend" => 40
    => volLenIn

float allowance = switch presetMode
    "Fast Response" => 0.15
    "Smooth Trend" => 0.3
    => allowanceIn

float threshold = switch presetMode
    "Fast Response" => 2.5
    "Smooth Trend" => 5.0
    => thresholdIn

//              ╔════════════════════════════════╗              //
//              ║       CUSUM DRIFT ENGINE       ║              //
//              ╚════════════════════════════════╝              //

var float ewVariance = na
var int sampleCount  = 0
var float upperSum   = 0.0
var float lowerSum   = 0.0
var int signalState  = 0

float priceChange = src - src[1]
float priorSigma  = sampleCount >= volLen ? math.sqrt(ewVariance) : na

if not na(priceChange) and not na(priorSigma) and priorSigma > 0
    float standardized = priceChange / priorSigma
    upperSum := math.max(0.0, upperSum + standardized - allowance)
    lowerSum := math.max(0.0, lowerSum - standardized - allowance)

if not na(priceChange)
    sampleCount += 1
    float alpha   = math.max(2.0 / (volLen + 1), 1.0 / sampleCount)
    float squared = priceChange * priceChange
    ewVariance := na(ewVariance) ? squared : ewVariance + alpha * (squared - ewVariance)

float sigma = sampleCount >= volLen ? math.sqrt(ewVariance) : na

if barstate.isconfirmed
    if signalState >= 0 and lowerSum > threshold
        signalState := -1
        upperSum    := 0.0
        lowerSum    := 0.0
    else if signalState <= 0 and upperSum > threshold
        signalState := 1
        upperSum    := 0.0
        lowerSum    := 0.0

int prevState  = nz(signalState[1])
float boundary = na
if signalState == 1
    boundary := src - sigma * (threshold + allowance - lowerSum)
else if signalState == -1
    boundary := src + sigma * (threshold + allowance - upperSum)

//              ╔════════════════════════════════╗              //
//              ║          SIGNAL ENGINE         ║              //
//              ╚════════════════════════════════╝              //

bool turnedBull    = signalState == 1 and prevState != 1
bool turnedBear    = signalState == -1 and prevState != -1
bool signalChanged = turnedBull or turnedBear

//              ╔════════════════════════════════╗              //
//              ║          VISUALIZATION         ║              //
//              ╚════════════════════════════════╝              //

[bullCol, bearCol] = switch colorPreset
    "Classic" => [#00ff00, #ff0000]
    "Aqua" => [#00d4ff, #ff8c00]
    "Cosmic" => [#49ffce, #9932cc]
    "Cyber" => [#00cccc, #ff6600]
    "Neon" => [#ffff00, #ff00ff]
    => [bullCustom, bearCustom]

color signalColor = signalState == 1 ? bullCol : signalState == -1 ? bearCol : chart.fg_color
bool sameTrend    = signalState == prevState
float evidence    = signalState == 1 ? lowerSum / threshold : signalState == -1 ? upperSum / threshold : 0.0
float zoneTransp  = 92.0 - 32.0 * math.min(evidence, 1.0)
color zoneColor   = showZone and sameTrend ? color.new(signalColor, zoneTransp) : na

plot(showGlow ? boundary : na, "Boundary Halo", sameTrend ? color.new(signalColor, 85) : na, 8, editable = false, display = display.pane)
plot(showGlow ? boundary : na, "Boundary Glow", sameTrend ? color.new(signalColor, 65) : na, 4, editable = false, display = display.pane)
boundaryPlot = plot(boundary, "CUSUM Boundary", sameTrend ? signalColor : na, 2, display = display.pane)
plot(boundary, "Boundary Level", signalColor, editable = false, display = display.all - display.pane)
pricePlot    = plot(src, "Price Reference", display = display.none, editable = false)
fill(boundaryPlot, pricePlot, color = zoneColor, title = "Evidence Zone")
plot(showPoints and signalChanged ? boundary : na, "Trend Change Halo", color.new(signalColor, 80), 14, plot.style_circles, editable = false, display = display.pane)
plot(showPoints and signalChanged ? boundary : na, "Trend Change Point", signalColor, 8, plot.style_circles, editable = false, display = display.pane)
plot(showPoints and signalChanged ? boundary : na, "Trend Change Center", chart.bg_color, 4, plot.style_circles, editable = false, display = display.pane)

barcolor(colorBars and signalState != 0 ? color.new(signalColor, barTrans) : na, title = "Trend Bar Color")
bgcolor(colorBg and signalState != 0 ? color.new(signalColor, bgTrans) : na, title = "Trend Background Color")

//              ╔════════════════════════════════╗              //
//              ║             ALERTS             ║              //
//              ╚════════════════════════════════╝              //

alertcondition(turnedBull, title = "Bullish Trend Signal", message = "CUSUM Drift Trend: BULLISH trend confirmed on {{exchange}}:{{ticker}} - {{interval}}")
alertcondition(turnedBear, title = "Bearish Trend Signal", message = "CUSUM Drift Trend: BEARISH trend confirmed on {{exchange}}:{{ticker}} - {{interval}}")
alertcondition(signalChanged, title = "Any Trend Change", message = "CUSUM Drift Trend: Trend signal changed on {{exchange}}:{{ticker}} - {{interval}}")

//              ╔════════════════════════════════╗              //
//              ║           CREATED BY           ║              //
//              ╚════════════════════════════════╝              //

// ██████╗ ██╗   ██╗ █████╗ ███╗   ██╗████████╗     █████╗ ██╗      ██████╗  ██████╗
//██╔═══██╗██║   ██║██╔══██╗████╗  ██║╚══██╔══╝    ██╔══██╗██║     ██╔════╝ ██╔═══██╗
//██║   ██║██║   ██║███████║██╔██╗ ██║   ██║       ███████║██║     ██║  ███╗██║   ██║
//██║▄▄ ██║██║   ██║██╔══██║██║╚██╗██║   ██║       ██╔══██║██║     ██║   ██║██║   ██║
//╚██████╔╝╚██████╔╝██║  ██║██║ ╚████║   ██║       ██║  ██║███████╗╚██████╔╝╚██████╔╝
// ╚══▀▀═╝  ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═══╝   ╚═╝       ╚═╝  ╚═╝╚══════╝ ╚═════╝  ╚═════╝